package com.gitee.sparta.core.bean;

import com.gitee.sparta.common.SpartaConstant;
import com.gitee.sparta.common.SpartaErrorEnum;
import com.gitee.sparta.common.exception.Assert;

/**
 * ack、renewal 标识的传递
 * <pre>
 * ack：f    renewal: f   ->  ackTag = 1
 * ack：t    renewal: f   ->  ackTag = 2
 * ack：f    renewal: t   ->  ackTag = 4
 * ack：t    renewal: t   ->  ackTag = 6
 * <pre>
 * 对于系统而言， 有效标识则是： 2、4
 *
 * @author zyred
 * @since v 0.0.1
 */
@SuppressWarnings("all")
public class SpartaChannel {

    /**
     * 重试间隔时间，单位：ms
     * 默认：3s
     * 将重试时间交给用户
     */
    private int retryIntervalTime = SpartaConstant.RETRY_INTERVAL;

    /**
     * 续签时间
     * 默认 30s
     * 将续签间隔时间交给用户
     */
    private int renewIntervalTime = SpartaConstant.RENEW_INTERVAL;

    /** 元数据 ID，UUID，全局唯一, 该 id 可以进行查询 **/
    private final String metaId;

    private final long expire;

    /** ack、rejoin 线程私有的标识 **/
    private final ThreadLocal<Integer> ackTag;

    public SpartaChannel(ThreadLocal<Integer> ackTag, String metaId, long expire) {
        this.ackTag = ackTag;
        this.metaId = metaId;
        this.expire = expire;
    }

    public void ack () {
        this.ack(true);
    }

    /**
     * ack 后的消息将会被删除，表明该消息被消费者成功消费
     * <p>
     * 未 ack 的消息将会进入 deny_tab redis set 集合中
     * 用户可以统一调用 SpartaTemplate#processDenys()
     * 对未 ack 的消息进行处理
     *
     * @param ack   true: ack， false：deny
     */
    private void ack (boolean ack) {
        if (!ack) {
            return;
        }
        ackTag.set((ackTag.get() == 1 << 2) ? (1 << 2 | 1 << 1) : (1 << 1));
    }

    /**
     * 该方法与 ack 相反
     * 如果调用了该方法，并且设置了 sparta.enable-deny-persistence=true 的情况
     * 完整的 topic 将会被存储到 deny_tab 中
     */
    public void deny () {
        ack(false);
    }


    public void renewal() {
        renewal(true);
    }

    /**
     * 续期，调用此方法后，在监听器 Callback#delayListener() 方法对 QueueJob 对象修改的任何字段将会失效
     * 换句话说就是，消费者在消费的方法内对 QueueJob 对象的任何字段做的修改，将会无效，重新进入队列的 QueueJob
     * 对象将会是第一次 add 的原生对象
     *
     * @param renewal    true: 重新入队列， false：不入队（等同于不调用此方法）
     */
    public void renewal (boolean renewal) {
        if (!renewal) {
            return;
        }
        ackTag.set((ackTag.get() == 1 << 1) ? (1 << 1 | 1 << 2) : (1 << 2));
    }

    /**
     * setRetryIntervalTime
     *
     * @param retryIntervalTime 重试间隔时间， 不同的业务重试间隔时间也不同
     */
    public void setRetryIntervalTime(int retryIntervalTime) {
        Assert.isTrue(retryIntervalTime < SpartaConstant.RETRY_INTERVAL, SpartaErrorEnum.RETRY_COUNT_VALUE_VALID);
        this.retryIntervalTime = retryIntervalTime;
    }

    /**
     * setRenewIntervalTime
     *
     * @param renewIntervalTime 续签间隔时间， 不同的业务重试间隔时间也不同
     */
    public void setRenewIntervalTime(int renewIntervalTime) {
        Assert.isTrue(renewIntervalTime < SpartaConstant.RENEW_INTERVAL, SpartaErrorEnum.RENEW_COUNT_VALUE_VALID);
        this.renewIntervalTime = renewIntervalTime;
    }

    public int getRetryIntervalTime() {
        return retryIntervalTime;
    }

    public int getRenewIntervalTime() {
        return renewIntervalTime;
    }

    public String getMetaId() {
        return metaId;
    }

    public long getExpire() {
        return expire;
    }
}
